﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model_Layer;
using Data_Access_Layer;
using System.Windows.Forms;


namespace Business_Logic_Layer
{
    public class BLLDijkstra
    {
        #region RunDijkstra method
        public List<Airport> RunDijkstra(Airport source, Airport target, string wClause, int deviationDays)
        {
            try
            {
                DBFlight flightDB = new DBFlight();
                List<Flight> edgeObjList = flightDB.GetAllFlights(true);

                //Method for only getting flights with more than -6 availble seats
                //List<Flight> edgeObjList = flightDB.GetCriteriaFlights(wClause, true);

                DBAirport airportDB = new DBAirport();
                List<Airport> verticeObjList = airportDB.GetAllAirports();

                List<Airport> dijkstraSolutionList = new List<Airport>();
                Airport u = source;
                #region Set Source.Dist to 0 and all other Airport vertices to infinite
                foreach (Airport v in verticeObjList)
                {
                    if (v.IdAirport == u.IdAirport)
                    {
                        v.Dist = 0; // Source airport
                    }
                    else
                    {
                        v.Dist = int.MaxValue; //set every other airportdistance in the list to infinity.
                        v.PreviousVertex = null; // undefined
                    }

                    //v.FlightList = new List<Flight>();
                    v.AdjFlight = new Flight();
                    v.Price = 0.0;
                }
                #endregion

                while (verticeObjList.Count > 0)
                {
                    u = SmallestDistInQ(verticeObjList);
                    verticeObjList.Remove(u);
                    dijkstraSolutionList.Add(u);

                    if (u.Dist == int.MaxValue)
                    {
                        break;
                    }

                    List<Flight> adjFlightList = AdjFlights(edgeObjList, u);

                    #region Relaxation step
                    int alt;
                    // for each neighbor v of u
                    for (int i = 0; i < adjFlightList.Count - 1; i++)
                    {
                        for (int j = 0; j < verticeObjList.Count; ++j)
                        {
                            #region Future implementation checking on chosen date
                            //Flight flightObj = new Flight();
                            //DateTime adjFTimeOfDeparture = DateTime.Parse(adjFlightList[i].Timeofdeparture);
                            //DateTime upperBoundFlight = adjFTimeOfDeparture.AddDays(deviationDays); // deviationDays is gotten through parameter
                            //DateTime lowerBoundFlight = adjFTimeOfDeparture.AddDays(-deviationDays);
                            //DateTime nowBoundFlight = DateTime.Now;

                            //if (adjFTimeOfDeparture > lowerBoundFlight && adjFTimeOfDeparture < upperBoundFlight && adjFTimeOfDeparture > nowBoundFlight)
                            //{
                            #endregion

                            //if the id of the current vertex equal the id of the destinationairport on the flight.
                                if (verticeObjList[j].IdAirport == adjFlightList[i].DestinationAirport.IdAirport)
                                {
                                    alt = u.Dist + adjFlightList[i].Traveltime;
                                    if (alt < verticeObjList[j].Dist)
                                    {
                                        verticeObjList[j].Dist = alt;
                                        verticeObjList[j].PreviousVertex = u;
                                        verticeObjList[j].AdjFlight = adjFlightList[i];
                                        verticeObjList[j].Price = adjFlightList[i].Price;

                                        #region Extra functionallity for optimization
                                        //verticeObjList[j].FlightList.Clear();
                                        //verticeObjList[j].FlightList.Add(adjFlightList[i]);
                                        //DecreaseKey(airportlist[j]);
                                        #endregion
                                        #region Test code for relaxation step
                                        //MessageBox.Show("Current v: " + verticeObjList[j].Name.ToString() + "\n" +
                                        //    "Dist: " + verticeObjList[j].Dist.ToString() + "\n" +
                                        //    "Airport ID: " + verticeObjList[j].IdAirport.ToString() + "\n" +
                                        //    "Previous vertex: " + verticeObjList[j].PreviousVertex.Name.ToString());
                                        #endregion
                                    }
                                    #region Extra functionality for optimization with several shortest paths of same length
                                    //else if (alt == verticeObjList[j].Dist)
                                    //{
                                    //    verticeObjList[j].Dist = alt;
                                    //    verticeObjList[j].PreviousVertex = u;
                                    //    verticeObjList[j].FlightList.Clear();
                                    //    verticeObjList[j].FlightList.Add(adjFlightList[i]);
                                    //}
                                    #endregion
                                }
                            //}
                        }
                    }
                    #endregion
                } // end while loop
                #region Test code for total distance which is to be moved.
                //int totalDist = 0;
                //foreach (Airport a in dijkstraSolutionList)
                //{
                //    totalDist += a.Dist;
                //}
                //MessageBox.Show("Num of elements in solution list: " + dijkstraSolutionList.Count.ToString() + "\n" +
                //    "Total traveltime: " + totalDist);
                #endregion
                #region Test code for the returned list.
                //foreach (Airport a in dijkstraSolutionList)
                //{
                //    MessageBox.Show("Vertex: " + a.Name.ToString() + "\n" + "Dist: " + a.Dist.ToString() + "\n" + "Airport ID: " + a.IdAirport.ToString()
                //    + "\n" + "Previous vertex: " + a.PreviousVertex.Name.ToString());
                //}
                #endregion

                dijkstraSolutionList = BacktrackDijkstra(dijkstraSolutionList, target);
                return dijkstraSolutionList;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
        #endregion

        #region Method for BacktrackDijkstra
        private List<Airport> BacktrackDijkstra(List<Airport> verticeObjList, Airport target)
        {
            List<Airport> solutionList = new List<Airport>();
            //loop through the list to find the actual target with this id on the solution list, if found, replace.
            foreach (Airport airportobj in verticeObjList)
            {
                if (airportobj.IdAirport == target.IdAirport)
                {
                    //replace -to be able to use target in dijkstra solution list. stop after the target element is found
                    target = airportobj;
                    break;
                }
            }
            //set current target with the one we found in dijkstra list
            Airport u = target;
            double price = 0.0;
            int totaltraveltime = 0;
            Flight startflight = new Flight();

            // While there is a parent to this Airport
            while (u.PreviousVertex != null)
            {
                solutionList.Add(u);  // Add Airport to solution

                // Summing up traveltime
                totaltraveltime += u.Dist;
                u.Dist = totaltraveltime;

                // Summing up price
                price += u.Price;
                u.Price = price;

                startflight = u.AdjFlight;

                u = u.PreviousVertex; // Add parent Airport as new Target 
            }

            //to prevent adding target to the list when the list is empty
            if (solutionList.Count > 0)
            {
                u.Dist = totaltraveltime;
                u.Price = price;
                u.AdjFlight = startflight;
                solutionList.Add(u);
            }

            return solutionList;
        }
        #endregion

        #region Methods used in Dijkstra
        #region Smallest distance in queue
        private Airport SmallestDistInQ(List<Airport> myQueue)
        {
            Airport smallestDist = new Airport();
            smallestDist.Dist = int.MaxValue;

            foreach (Airport q in myQueue)
            {
                if (q.Dist < smallestDist.Dist) 
                {
                    smallestDist = q;
                }
            }

            return smallestDist;
        }
        #endregion

        #region Get all adjacent Flights
        private List<Flight> AdjFlights(List<Flight> flightList, Airport u)
        {
            List<Flight> adjFlightList = new List<Flight>();
            //if start airport id in flight list has the same id in airport list, pick the entire flight object
            //and add to flight list(adjFlightList)
            foreach (Flight f in flightList)
            {
                if (u.IdAirport == f.StartAirport.IdAirport)
                {
                    // if f.desparturetime < f.previous.arrivaltime + 1 hours.
                    adjFlightList.Add(f);
                }
            }

            return adjFlightList;
        }
        #endregion
        #endregion
    }
}